using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;

/// <summary>This struct is used as a ESE bookmarks.</summary>
/// <remarks>Besides, it's used virtually anywhere byte arrays needs to be stored.</remarks>
public struct ByteArray : IEquatable<ByteArray>, IComparable<ByteArray>
{
	byte[] m_data;

	/// <summary>Construct from byte[]</summary>
	/// <remarks>This constructor doesn't copy the data, instead it saves the reference to the array.</remarks>
	/// <param name="data"></param>
	public ByteArray( byte[] data ) { m_data = data; }

	/// <summary>Construct from IEnumerable&lt;byte&gt;</summary>
	/// <param name="data"></param>
	public ByteArray( IEnumerable<byte> data ) { m_data = data.ToArray(); }

	[DllImport( "msvcrt.dll" )]
	private static extern int memcmp( byte[] arr1, byte[] arr2, int cnt );

	static bool CompareByteArrays( byte[] b1, byte[] b2 )
	{
		if( null == b1 || 0 == b1.Length )
			return ( null == b2 || 0 == b2.Length );

		if( null == b2 || 0 == b2.Length ) return false;

		if( b1.Length != b2.Length ) return false;
		return ( memcmp( b1, b2, b1.Length ) == 0 );
	}

	/// <summary>Compare for equality</summary>
	/// <param name="a"></param>
	/// <param name="b"></param>
	/// <returns>True of they are equal.</returns>
	/// <remarks>Two empty byte arrays are always equal.</remarks>
	public static bool operator ==( ByteArray a, ByteArray b )
	{
		return CompareByteArrays( a.m_data, b.m_data );
	}

	/// <summary>Compare for equality</summary>
	/// <param name="a"></param>
	/// <param name="b"></param>
	/// <returns>True of they are not equal.</returns>
	public static bool operator !=( ByteArray a, ByteArray b )
	{
		return !CompareByteArrays( a.m_data, b.m_data );
	}

	/// <summary>Compute the hash function.</summary>
	/// <param name="data"></param>
	/// <returns></returns>
	public static int ComputeHash( byte[] data )
	{
		if( null == data ) return 0;
		unchecked
		{
			const int p = 16777619;
			int hash = (int)2166136261;

			for( int i = 0; i < data.Length; i++ )
				hash = ( hash ^ data[ i ] ) * p;

			hash += hash << 13;
			hash ^= hash >> 7;
			hash += hash << 3;
			hash ^= hash >> 17;
			hash += hash << 5;
			return hash;
		}
	}

	/// <summary>Compare for equality with either another ByteArray, or byte[]</summary>
	public override bool Equals( object obj )
	{
		if( obj is ByteArray )
			return CompareByteArrays( this.m_data, ( (ByteArray)obj ).m_data );
		if( obj is byte[] )
			return CompareByteArrays( this.m_data, (byte[])obj );
		return false;
	}

	/// <summary>Compute the hash function.</summary>
	/// <returns></returns>
	public override int GetHashCode()
	{
		return ComputeHash( m_data );
	}

	bool IEquatable<ByteArray>.Equals( ByteArray that )
	{
		return CompareByteArrays( this.m_data, that.m_data );
	}

	/// <summary>Implement IComparable&lt;ByteArray&gt;.CompareTo method.</summary>
	/// <param name="that"></param>
	/// <returns></returns>
	public int CompareTo( ByteArray that )
	{
		if( isEmpty() )
		{
			if( that.isEmpty() ) return 0;
			return -1;
		}
		if( that.isEmpty() ) return 1;

		int resData = memcmp( data, that.data, Math.Min( data.Length, that.data.Length ) );
		if( resData != 0 ) return resData;
		return data.Length.CompareTo( that.data.Length );
	}

	/// <summary>Get the data.</summary>
	public byte[] data { get { return m_data; } }

	/// <summary>Returns true if this array is empty.</summary>
	/// <returns></returns>
	public bool isEmpty() { return m_data == null || m_data.Length <= 0; }

	/// <summary>An empty ByteArray</summary>
	public static ByteArray Empty { get { ByteArray res = new ByteArray(); res.m_data = null; return res; } }

	/// <summary>Create new byte array by concatenating several arrays.</summary>
	/// <param name="arrs"></param>
	/// <returns></returns>
	public static ByteArray Join( params object[] arrs )
	{
		IEnumerable<byte> res = new byte[ 0 ];
		foreach( object a in arrs )
		{
			if( a is ByteArray )
				res = res.Concat( ( (ByteArray)a ).data );
			else if( a is byte[] )
				res = res.Concat( a as byte[] );
			else
				throw new ArgumentException( "Type mismatch: unable to concatenate an object of type '" + a.GetType().Name + "'." );
		}
		return new ByteArray( res );
	}
}
